/* @flow */

import Regexp from 'path-to-regexp'
import {cleanPath} from './util/path'
import {assert, warn} from './util/warn'

export function createRouteMap(routes: Array<RouteConfig>,
                               oldPathList?: Array<string>,
                               oldPathMap?: Dictionary<RouteRecord>,
                               oldNameMap?: Dictionary<RouteRecord>): {
    pathList: Array<string>;
    pathMap: Dictionary<RouteRecord>;
    nameMap: Dictionary<RouteRecord>;
} {
    // the path list is used to control path matching priority
    const pathList: Array<string> = oldPathList || []
    // $flow-disable-line
    const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
    // $flow-disable-line
    const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)

    routes.forEach(route => {
        addRouteRecord(pathList, pathMap, nameMap, route)
    })

    // ensure wildcard routes are always at the end
    //保证通配符总是在最后
    for (let i = 0, l = pathList.length; i < l; i++) {
        if (pathList[i] === '*') {
            //pathList.splice(i, 1)的结果为["*"]，注意是数组，所以需要取出第一个成员
            pathList.push(pathList.splice(i, 1)[0])
            l--
            i--
        }
    }

    return {
        pathList,
        pathMap,
        nameMap
    }
}

/** 示例：
 *  {
 *       path: '/a',
 *       component: A,
 *       alias: '/A',
 *
 *       children:[{
 *          path: 'b',
 *          component:B,
 *          alias:'B'
 *       }]
 *  }
 *  在解析该路由时，会生成六个路由信息，用路径path的值表示分别为：
 *  /a /A /a/b /a/B /A/b /A/B
 *  比如path为/A/B时，其matchAs为/A/b，而/A/b的matchAs又为
 *  /a/b，这样就找到了最原始的可供使用的路径
 */
function addRouteRecord(pathList: Array<string>,
                        pathMap: Dictionary<RouteRecord>,
                        nameMap: Dictionary<RouteRecord>,
                        route: RouteConfig,
                        parent?: RouteRecord,
                        //matchAs的作用是什么？
                        //答：可查看方法之前的示例
                        matchAs?: string) {
    const {path, name} = route
    if (process.env.NODE_ENV !== 'production') {
        //RouteConfig对象必须提供path属性
        assert(path != null, `"path" is required in a route configuration.`)
        assert(
            typeof route.component !== 'string',
            `route config "component" for path: ${String(path || name)} cannot be a ` +
            `string id. Use an actual component instead.`
        )
    }

    const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}
    const normalizedPath = normalizePath(
        path,
        parent,
        pathToRegexpOptions.strict
    )

    if (typeof route.caseSensitive === 'boolean') {
        pathToRegexpOptions.sensitive = route.caseSensitive
    }

    const record: RouteRecord = {
        path: normalizedPath,
        regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
        components: route.components || {default: route.component},
        instances: {},
        name,
        parent,
        matchAs,
        redirect: route.redirect,
        beforeEnter: route.beforeEnter,
        meta: route.meta || {},
        props: route.props == null
            ? {}
            : route.components
                ? route.props
                : {default: route.props}
    }

    if (route.children) {
        // Warn if route is named, does not redirect and has a default child route.
        // If users navigate to this route by name, the default child will
        // not be rendered (GH Issue #629)
        if (process.env.NODE_ENV !== 'production') {
            if (route.name && !route.redirect && route.children.some(child => /^\/?$/.test(child.path))) {
                warn(
                    false,
                    `Named Route '${route.name}' has a default child route. ` +
                    `When navigating to this named route (:to="{name: '${route.name}'"), ` +
                    `the default child route will not be rendered. Remove the name from ` +
                    `this route and use the name of the default child route for named ` +
                    `links instead.`
                )
            }
        }
        route.children.forEach(child => {
            const childMatchAs = matchAs
                ? cleanPath(`${matchAs}/${child.path}`)
                : undefined
            addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
        })
    }

    if (route.alias !== undefined) {
        //别名可以是数组
        const aliases = Array.isArray(route.alias)
            ? route.alias
            : [route.alias]

        aliases.forEach(alias => {
            const aliasRoute = {
                path: alias,
                children: route.children
            }
            addRouteRecord(
                pathList,
                pathMap,
                nameMap,
                aliasRoute,
                parent,
                record.path || '/' // matchAs
            )
        })
    }

    if (!pathMap[record.path]) {
        pathList.push(record.path)
        pathMap[record.path] = record
    }

    if (name) {
        if (!nameMap[name]) {
            nameMap[name] = record
        } else if (process.env.NODE_ENV !== 'production' && !matchAs) {
            //任意级别的route的name属性的值都不可重复，
            //示例：
            /**
             * {
             *      path:'/a',
             *      name:'a',
             *      children:{
             *          path:'/b',
             *          name:'a'
             *      }
             * }
             *
             * 此时是错误的，因为两个name属性的值不可相同
             */
            warn(
                false,
                `Duplicate named routes definition: ` +
                `{ name: "${name}", path: "${record.path}" }`
            )
        }
    }
}

function compileRouteRegex(path: string, pathToRegexpOptions: PathToRegexpOptions): RouteRegExp {
    const regex = Regexp(path, [], pathToRegexpOptions)
    if (process.env.NODE_ENV !== 'production') {
        const keys: any = Object.create(null)
        regex.keys.forEach(key => {
            warn(!keys[key.name], `Duplicate param keys in route with path: "${path}"`)
            keys[key.name] = true
        })
    }
    return regex
}

function normalizePath(path: string, parent?: RouteRecord, strict?: boolean): string {
    if (!strict) path = path.replace(/\/$/, '')
    if (path[0] === '/') return path //如果是绝对路径，则直接返回
    if (parent == null) return path
    return cleanPath(`${parent.path}/${path}`)
}
